<!DOCTYPE html>
<html><meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>五大IO模型</title>
<style type="text/css">
    body{
        background-color: #3C3F41;
    }
    a{
        text-decoration: none;
        color: #fbfbff;
    }
</style>
<body><head>
    <style type="text/css">
        nav ul {
            list-style-type: none;
            margin: 0;
            padding: 0;
            overflow: hidden;
            background-color: #333;
            position: -webkit-sticky;  
            position: sticky;
            top: 0;
        }

        nav li {
            float: left;
        }

        nav li a {
            display: block;
            color: white;
            text-align: center;
            padding: 14px 16px;
            text-decoration: none;
        }

         
        nav li a:hover {
            background-color: #111;
        }
        .active {
            background-color: #4CAF50;
        }
    </style>
</head>

<nav>
    <ul>
        
        
        <li><a href="/blog/">home</a></li>
         
        
        <li><a href="/blog/post">博文</a></li>
         
        
        <li><a href="/blog/about">about</a></li>
         
    </ul>
</nav>
<div id="content" style="margin: auto;width: 50%;padding: 10px; color: #ffffff;height: 100%">
    <article>
        <header>
            <h1 class="post-title">五大IO模型</h1>
        </header>
        <p>在学习NIO之前，我们非常有必要了解一下操作系统中的各种IO模型，否则是不会理解NIO的实现的．</p>
<p>这篇文章是翻译I/O Multiplexing: The select and poll Functions <a href="https://notes.shichao.io/unp/ch6/">https://notes.shichao.io/unp/ch6/</a> 这篇文章中的前半部分关于IO模型的部分．这篇文章中，还对select()等系统调用有更加深入的介绍，各位不妨读一下．</p>
<p>正文
在Unix下，我们有五种不同的IO模型，分别是：</p>
<p>阻塞IO(Blocking IO)
非阻塞IO(Nonblocking IO)
I/O复用(I/O Multiplexing)
信号驱动IO(signal driven I/O)
异步IO(Asynchronous IO)
对于一个读操作来说，一般会经过下面两个过程：</p>
<p>等待数据就绪．比如说，对于一个网络连接来说，就是等待数据通过连接到达主机．当数据到达主机时，把数据拷贝到内核中的缓冲区．
将数据从内核拷贝到进程．即把数据从内核的缓冲区拷贝到应用程序的缓冲区．
阻塞IO
最常用的IO模型就是阻塞IO．默认情况下，全部的socket都是阻塞的．其处理过程如下图所示：</p>
<p>在这个例子中，我们会通过UDP而不是TCP来举例，因为对于UDP来说，等待数据就绪这一步更加直观：要不就是收到了一个数据报，要不就是没收到一个数据报．但是对于TCP来说，还有很多额外的变量．</p>
<p>上图中的recvfrom是一个系统调用．当我们执行一次系统调用的时候，有一次从用户态到内核态的切换．</p>
<p>从上图中我们可以看到，进程调用recvfrom之后，这个系统调用并不会立即返回，它会等到数据报到达并且被拷贝到应用程序的缓冲区中，或者出现了一个错误，才会返回．我们称这个过程是阻塞的，应用程序只有在数据报被放入缓冲区之后，才能继续进行．</p>
<p>非阻塞IO
非阻塞IO和阻塞IO相对，它会告诉内核，&ldquo;当我要你完成的IO操作不能完成时，不要让进程阻塞，你给我返回一个错误就行了&rdquo;．过程如下图所示：</p>
<p>在上面的三个recvfrom操作中，由于数据并没有就绪，所以内核返回了一个EWOULDBLOCK错误．
在第四个recvfrom中，数据已经就绪了，并且已经被拷贝到我们的应用程序的缓冲区了，内核返回一个OK，然后我们的应用程序处理这些数据．
我们可以看到，在这种模型中，我们需要使用轮询的方式来确定数据到底是否就绪．尽管这会浪费CPU时间，但是仍然是比较常见的模型，一般是在系统函数中用到．</p>
<p>I/O多路复用
在I/O多路复用中，我们会调用select()或者poll()，并且阻塞在这两个系统调用上．而不是阻塞在recvfrom这个实际的IO操作的系统调用上．下面是I/O多路复用模型的过程图：</p>
<p>从上图中，我们可以看到，我们会阻塞在select()这个系统调用上，并等待数据到达．当select()告诉我们数据到达时，再通过recvfrom系统调用将数据拷贝到应用程序的缓冲区．</p>
<p>看到这里，如果各位不了解select()，可能就会有一个疑问．你这不是脱了裤子放屁吗？这不是还是跟阻塞IO模型一样，还是阻塞吗？只不过现在不是阻塞在recvfrom上，而是阻塞在select上而已．而且，现在还多了一次系统调用，那效率不是更低吗？</p>
<p>多了一次系统调用，确实是I/O多路复用模型的缺点．但是存在即合理，它也有优点．</p>
<p>它的优点在于，select可以同时监听多个文件描述符，以及感兴趣的事件．所以，我们可以在一个线程中完成之前需要好多个线程才能完成的事情．</p>
<p>比如，我们想要同时从一个接受来自Socket的数据，以及从文件中读数据．在阻塞IO模型中，我们会这么做：</p>
<p>1．创建一个线程A，在其中创建一个Socket Server，并通过它的accept()方法，等待客户端的连接并处理数据
2．创建一个线程B，在其中打开文件并且读数据．
这就需要两个线程，对吧？</p>
<p>而且我们又知道，线程之间的切换是有开销的，也是需要涉及到用户态到内核态的转换．</p>
<p>而我们在I/O多路复用模型中，可以这样做：</p>
<p>1．通过注册函数告诉系统，应用程序对于Socket的读事件以及文件的读事件感兴趣
2．通过轮询调用select()方法，查看哪些我们感兴趣的事件已经发生了
3．在同一个线程中，依次进行对应的操作
我们可以看到，在这里我们只需要用一个线程就可以做到在阻塞IO中我们需要两个线程才能做到的事情．这就是I/O复用中的复用的含义．</p>
<p>信号驱动IO
信号驱动IO使用信号量机制，它告诉内核，当文件描述符准备就绪时，通过SIGIO信号通知我们．过程如下：</p>
<p>我们首先通过sigaction系统调用安装一个事件处理器．这个操作会立即返回．所以我们的应用程序会继续运行，而不会阻塞．
当数据准备就绪时，内核会给我们的应用程序发出一个SIGIO信号，我们可以继续进行下面的处理：
在信号处理器中，通过recvfrom系统调用将数据从内核缓冲区读取到应用程序缓冲区中
告诉应用程序从缓冲区读取数据并且处理
这种模型的优点是，在等待数据就绪时，应用程序并不会被阻塞．应用程序可以继续运行，只需要在数据就绪时，让时间处理器通知它即可．</p>
<p>异步IO
异步IO模型跟事件驱动IO模型类似，也是告诉内核，在一定情况下通知我们．但是它跟事件驱动IO模型不同的是，在事件驱动IO模型中，内核会在数据就绪，即数据被拷贝到内核缓冲区时，通知我们．而在异步IO中，内核会在整个操作都被完成，即数据从内核缓冲区拷贝到应用程序缓冲区时，通知我们．如下图所示：</p>
<p>我们调用aio_read这个系统调用，并且给内核传递下面的数据：
文件描述符，缓冲区指针，缓冲区大小
文件偏移量
当整个操作完成时，如何通知我们
这个系统调用会立即返回，在整个操作完成之前，不会被阻塞
五种IO模型的比较</p>
<p>同步IO和异步IO
POSIX中，定义了下面的两个术语：</p>
<p>同步IO：在整个IO操作完成之前，会导致应用程序阻塞的IO操作叫做同步IO
异步IO：在整个IO操作完成之前，不会导致应用程序阻塞的IO操作叫做异步IO
我们可以看到，上面我们定义的五个IO模型，前四个(阻塞IO模型，非阻塞IO模型，IO复用模型，信号驱动模型)都属于同步IO，而只有异步IO模型属于异步IO．</p>
<p>注意
原文中还有更多关于select等IO复用的详细介绍，所以强烈建议各位读一下原文．</p>
<p>作者：AlstonWilliams
链接：https://www.jianshu.com/p/f444a74babcf
来源：简书
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
参考资料: <a href="https://jiges.github.io/2018/02/07/%E4%BA%94%E5%A4%A7IO%E6%A8%A1%E5%9E%8B/#more">https://jiges.github.io/2018/02/07/%E4%BA%94%E5%A4%A7IO%E6%A8%A1%E5%9E%8B/#more</a></p>

        <br />

        <div>
            
            <a href="https://alittlesmile.gitee.io/blog/post/computer/%E7%A1%AC%E7%9B%98%E6%8E%A5%E5%8F%A3%E6%A0%87%E5%87%86/">&lt; 硬盘接口标准</a>
            

            
            <a href="https://alittlesmile.gitee.io/blog/post/docker/yum%E5%AE%89%E8%A3%85docker/" style="float:right">yum 安装docker ></a>
            
        </div>
    </article>

</div></body>
</html>
